using Commons.Models.Capabilities;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Collections;

namespace Commons.Utils.Extractors
{
	public class DataExtractor : IDataExtractor
	{
		public const string ATTRIBUTE_REGEX = "(\\{[a-zA-Z0-9&-_]+(\\.[a-zA-Z0-9&-_]+)*\\})";
		public const string CAPABILITY_REGEX = "(\\(\\w+\\.\\w+(\\.\\w+)*\\))";
		public static Regex COMPLETE_PATTERN = new Regex("(" + "^" + ATTRIBUTE_REGEX + "?" + CAPABILITY_REGEX + "?" + "$" + "|" + "^" + ATTRIBUTE_REGEX + "?" + "$" + "|" + "^" + CAPABILITY_REGEX + "?" + "$" + ")");
		public static Regex CAPABILITY_PATTERN = new Regex(CAPABILITY_REGEX);
		public static Regex ATTRIBUTE_PATTERN = new Regex(ATTRIBUTE_REGEX);

		public IDataExtractorParametizer Parametizer { get; set; }


		public static DataExtractor GetDefault()
		{
			Dictionary<string, Type> capabilities = new Dictionary<string, Type>();
			capabilities.Add("taggable", typeof(Taggable));

			DataExtractorParametizer parametizer = new DataExtractorParametizer();
			parametizer.Capabilities = capabilities;

			DataExtractor dataExtractor = new DataExtractor();
			dataExtractor.Parametizer = parametizer;

			return dataExtractor;
		}

		public T GetValue<T>(string regex, object target)
		{
			return (T) GetValue(GetValuePath(regex), target);
		}

		/// <summary>
		/// Extract the value of the target object indicated by the regex parameter.
		/// Throws InvalidPathException in case the regex passed by parameter is not the correct form
		/// </summary>
		/// <returns>
		/// The value.
		/// </returns>
		/// <param name='regex'>
		/// Regex.
		/// </param>
		/// <param name='target'>
		/// Target.
		/// </param>
		public object GetValue(string regex, object target)
		{
			return GetValue(GetValuePath(regex), target);
		}

		/// <summary>
		/// Extract the value of the target object indicated by the path parameter.
		/// </summary>
		/// <returns>
		/// The value.
		/// </returns>
		/// <param name='path'>
		/// Path.
		/// </param>
		/// <param name='target'>
		/// Target.
		/// </param>
		public object GetValue(ValuePath path, object target)
		{
			object value = null;

			if (path.IsFromAttribute())
			{
				value = GetValue(path.AttributePath, target);
				if (value != null && path.IsFromCapability())
				{
					value = GetValueFromCapability(path, value);
				}
			}
			else
			{
				value = GetValueFromCapability(path, target);
			}

			return value;
		}

		public T GetValue<T>(ValuePath path, object target)
		{
			object value = GetValue(path, target);
			if (value != null && !(value is T))
			{
				throw new Exception("The value " + value + " on path " + path + " is not of type: " + typeof(T).Name);
			}

			return (T) value;
		}

		public ValuePath GetValuePath(string regex)
		{
			// throws exception if the path is not in the correct form
			if (!COMPLETE_PATTERN.IsMatch(regex))
			{
				throw new InvalidPathException(regex);
			}

			ValuePath path = new ValuePath();

			if (ATTRIBUTE_PATTERN.IsMatch(regex))
			{
				// {attribute}
				path.AttributePath = new List<string>();
				string rawValue = GetRawValue(ATTRIBUTE_PATTERN, regex); //without {}
				if (rawValue.Contains("."))
				{
					path.AttributePath.AddRange(UppercaseFirst(rawValue.Split('.')));
				}
				else
				{
					path.AttributePath.Add(UppercaseFirst(rawValue));
				}
			}

			if (CAPABILITY_PATTERN.IsMatch(regex))
			{
				// {capability}(attribute.attribute)
				string rawValue = GetRawValue(CAPABILITY_PATTERN, regex);
				string[] plain = rawValue.Split('.');
				path.Capability = plain[0];
				path.CapabilityAttributePath = new List<string>(plain.Length - 1);
				for (int i = 1; i < plain.Length; i++)
				{
					path.CapabilityAttributePath.Add(UppercaseFirst(plain[i]));
				}
			}

			return path;
		}

		private string[] UppercaseFirst(string[] strings)
		{
			for (int i = 0; i < strings.Length; i++)
			{
				strings[i] = UppercaseFirst(strings[i]);
			}

			return strings;
		}

		private string UppercaseFirst(string s)
		{
			return s.Length > 0 ? char.ToUpper(s[0]) + s.Substring(1) : s;
		}

		private object GetValue(List<String> attributePath, object rootTarget)
		{
			object current = rootTarget;
			foreach (string attribute in attributePath)
			{
				if (current == null)
				{
					// Debug.LogWarning("Cannot extract " + attribute + " from null");
					return null;
				}
				// avoid using (sloooooow) reflection if current object is just an IDictionary
				if (current is IDictionary)
				{
					IDictionary dictionary = (IDictionary)current;
					current = dictionary[attribute];
				}
				else
				{
					current = ReflectionHelper.GetProperty(current, attribute);
				}
			}

			return current;
		}

		private string GetRawValue(Regex regex, string value)
		{
			Match match = regex.Match(value);
			if (match.Success)
			{
				return value.Substring(match.Index + 1, match.Length - 2);
			}
			else
			{
				return "";
			}
			
		}

		private object GetValueFromCapability(ValuePath path, object obj)
		{
			if (obj is ICapable)
			{
				Type capability = Parametizer.GetCapability(path.Capability);
				if (capability != null)
				{
					ICapable capable = (ICapable) obj;
					if (capable.HasCapability(capability))
					{
						return GetValue(path.CapabilityAttributePath, capable.GetCapability(capability));
					}
				}
			}

			return null;
		}
	}
}